package com.example.demo.common.file.service;

import com.alibaba.fastjson.JSONObject;

import com.alibaba.fastjson.TypeReference;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.example.demo.common.exception.EbsException;
import com.example.demo.common.persistence.model.SysDocFile;
import com.example.demo.common.vo.EbsFile;
import com.example.demo.core.cache.CacheManager;
import com.example.demo.core.constant.Constants;
import com.example.demo.core.util.EntityUtil;
import com.example.demo.core.util.ExecTime;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 功能描述：oss文件存储实现。
 */
@Service
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class OssFileService extends AbstractFileService implements FileService {

    private static Logger logger = LoggerFactory.getLogger(OssFileService.class);

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;

    private static String endpoint;
    private static String bucketName;
    private volatile static OSSClient client;
    private Object lock = new Object();

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(String local, String module1, String module2) {

        File file = new File(local);
        if (file.exists()) {
            return putFile(file, module1, module2);
        }
        return "";
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(File local, String module1, String module2) {

        String fileName = local.getName();

        String fileId = putFile(local, module1, module2, fileName);
        return fileId;
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(MultipartFile local, String module1, String module2) {

        try {
            String checkSum = createChecksum(local.getInputStream());
            return putFile(local.getInputStream(), module1, module2, local.getOriginalFilename(), checkSum);
        } catch (IOException e) {
            throw new EbsException(e);
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(String local, String module1, String module2, String rename) {

        File file = new File(local);
        if (file.exists()) {
            return putFile(file, module1, module2, rename);
        }
        return "";
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(File local, String module1, String module2, String rename) {

        if (!local.exists()) return null;

        try {
            InputStream input = new FileInputStream(local);
            String checkSum = createChecksum(new FileInputStream(local));
            String fileId = putFile(input, module1, module2, rename, checkSum);
            return fileId;
        } catch (Exception e) {
            throw new EbsException(e);
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(InputStream local, String module1, String module2, String rename) {

        return putFile(local, module1, module2, rename, "");
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(MultipartFile local, String module1, String module2, String rename) {
        try {
            String checkSum = createChecksum(local.getInputStream());
            return putFile(local.getInputStream(), module1, module2, rename, checkSum);
        } catch (IOException e) {
            throw new EbsException(e);
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String putFile(InputStream local, String module1, String module2, String rename, String checkSum) {

        try {
            /** 文件排重
             if(StringUtils.isNotEmpty(checkSum)) {
             SysDocFile sysDocFileOld = sysDocFileMapper.selectByChecksum(checkSum);
             if(sysDocFileOld != null && StringUtils.isNotEmpty(sysDocFileOld.getId())) {

             if(!StringUtils.endsWithIgnoreCase(sysDocFileOld.getFileName(), rename) ||
             !StringUtils.endsWithIgnoreCase(sysDocFileOld.getModule1(), module1) ||
             !StringUtils.endsWithIgnoreCase(sysDocFileOld.getModule2(), module2)) {
             sysDocFileOld.setId(getUUID());
             sysDocFileOld.setFileName(rename);
             sysDocFileOld.setModule1(module1);
             sysDocFileOld.setModule2(module2);
             sysDocFileMapper.insert(sysDocFileOld);
             }
             return sysDocFileOld.getId();
             }
             }*/

            String fileName = rename;
            Integer fileSize = local.available();

            String remoteFilePath = separator;
            String companyCode = getCompanyCode();
            String dateStr = DateFormatUtils.format(new Date(), "yyyyMMdd");
            String fileId = getUUID();
            remoteFilePath += companyCode + separator;
            remoteFilePath += dateStr + separator;
            remoteFilePath += module1 + separator;
            remoteFilePath += module2 + separator;
            remoteFilePath += fileId + "." + FilenameUtils.getExtension(fileName);

            uploadFile(remoteFilePath, local);

            SysDocFile sysDocFile = new SysDocFile();
            EntityUtil.setValue(sysDocFile);
            sysDocFile.setId(fileId);
            sysDocFile.setFileName(fileName);
            sysDocFile.setFilePath(remoteFilePath);
            sysDocFile.setFileSize(fileSize);
            sysDocFile.setFileType(FilenameUtils.getExtension(rename));
            sysDocFile.setModule1(module1);
            sysDocFile.setModule2(module2);
            sysDocFile.setServerNode(configServiceImpl.getString("ftp_server_node", "a"));
            sysDocFile.setFileChecksum(checkSum);
            sysDocFile.setCreateTime(new Date());

            sysDocFileMapper.insert(sysDocFile);
            return fileId;
        } catch (Exception e) {
            throw new EbsException(e);
        }
    }

    public EbsFile getFileById(String fileId) {

        SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);

        return getEbsFile(sysDocFile);
    }

    public EbsFile getFileById(String fileId, String accessCode) {

        SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
        if (StringUtils.equals(sysDocFile.getAccessCode(), accessCode)) {
            return getEbsFile(sysDocFile);
        }
        throw new EbsException("提取码错误");
    }

    public EbsFile getFileById(String[] fileIds) {

        ZipOutputStream zos = null;
        InputStream input = null;
        try {
            String filePath = getTempFile("download", "zip", "zip");
            String fileDir = FilenameUtils.getFullPath(filePath);
            mkdirs(fileDir);
            File file = new File(filePath);

            zos = new ZipOutputStream(new FileOutputStream(file));
            ZipEntry ze = null;
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int readLen = 0;

            EbsFile ebsFile = null;
            int ide = 0;
            String fileName = "打包文件.zip";
            boolean rename = false;//是否重命名的标识
            for (String fileId : fileIds) {
                ide += 1;
                ebsFile = getFileById(fileId);
                //根据当前文件组首个文件所属人获取所属人公司
                if (!rename && null != ebsFile) {
                    fileName = "file"+fileId+".zip";
                    rename = true;
                }
                ze = new ZipEntry("_" + ide + ebsFile.getFileName());
                zos.putNextEntry(ze);
                input = ebsFile.getInputStream();
                while ((readLen = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {
                    zos.write(buffer, 0, readLen);
                }
                IOUtils.closeQuietly(input);
            }
            EbsFile ebsZipFile = new EbsFile();
            ebsZipFile.setFile(file);
            ebsZipFile.setFileName(fileName);

            return ebsZipFile;
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(zos);
        }
    }

    public EbsFile getGroupFileById(String[] fileIds) {
        ZipOutputStream zosAll = null;
        ZipOutputStream zos = null;
        ZipEntry zeAll = null;
        InputStream input = null;
        InputStream inputAll = null;
        try {
            String filePath = getTempFile("download", "zip", "zip");
            String filePathAll = getTempFile("downloadAll", "zip", "zip");
            String fileDir = FilenameUtils.getFullPath(filePath);
            String fileDirAll = FilenameUtils.getFullPath(filePathAll);
            mkdirs(fileDir);
            mkdirs(fileDirAll);
            File file = null;
            File fileAll = null;

            ZipEntry ze = null;
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int readLenAll = 0;

            EbsFile ebsFile = null;
            String allFileName = "全部打包文件.zip";
            String fileName = "";
            boolean rename = false;//是否重命名的标识
            EbsFile ebsZipFile;

            fileAll = new File(filePathAll);
            zosAll = new ZipOutputStream(new FileOutputStream(fileAll));
            for (int i = 0; i < fileIds.length; i++) {
                String[] files = fileIds[i].split(",");
                fileName = "打包文件" + (i + 1) + ".zip";
                rename = false;//是否重命名的标识
                ebsZipFile = new EbsFile();
                file = new File(filePath);
                int readLen = 0;
                int index = 0;
                zos = new ZipOutputStream(new FileOutputStream(file));
                for (String fileId : files) {
                    ebsFile = getFileById(fileId);
                    //根据当前文件组首个文件所属人获取所属人公司
                    if (!rename && null != ebsFile) {
                        fileName = "file"+fileId+".zip";
                        rename = true;
                    }
                    index += 1;
                    ze = new ZipEntry("_" + index + ebsFile.getFileName());
                    zos.putNextEntry(ze);
                    input = ebsFile.getInputStream();
                    while ((readLen = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {
                        zos.write(buffer, 0, readLen);
                    }
                    IOUtils.closeQuietly(input);
                }
                IOUtils.closeQuietly(zos);

                ebsZipFile.setFile(file);
                ebsZipFile.setFileName(fileName);

                zeAll = new ZipEntry("_" + (i + 1) + ebsZipFile.getFileName());
                zosAll.putNextEntry(zeAll);
                inputAll = ebsZipFile.getInputStream();
                while ((readLenAll = inputAll.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) {
                    zosAll.write(buffer, 0, readLenAll);
                }
                IOUtils.closeQuietly(inputAll);
            }
            EbsFile ebsZipFileAll = new EbsFile();
            ebsZipFileAll.setFile(fileAll);
            ebsZipFileAll.setFileName(allFileName);
            return ebsZipFileAll;
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(input);
            IOUtils.closeQuietly(zos);
            IOUtils.closeQuietly(inputAll);
            IOUtils.closeQuietly(zosAll);
        }
    }

    public EbsFile copyFileById(String fileId, OutputStream out) {

        SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
        return getEbsFile(sysDocFile, out, false);
    }

    public EbsFile getFileByPath(String filePath) {
        SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
        if (sysDocFile == null) {
            sysDocFile = new SysDocFile();
            sysDocFile.setFilePath(filePath);
            sysDocFile.setFileName(FilenameUtils.getName(filePath));
        }
        return getEbsFile(sysDocFile);
    }

    public EbsFile copyFileByPath(String filePath, OutputStream out) {

        SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
        return getEbsFile(sysDocFile, out, false);
    }

    public String getFileNameById(String fileId) {

        if (StringUtils.isEmpty(fileId)) return "";

        SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);

        if (sysDocFile == null) return "";

        return sysDocFile.getFileName();
    }

    public String getFileNameByPath(String filePath) {

        SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
        return sysDocFile.getFileName();
    }

    public SysDocFile getSysDocFileById(String fileId) {
        SysDocFile sysDocFile = sysDocFileMapper.selectById(fileId);
        return sysDocFile;
    }

    public List<SysDocFile> getSysDocFileByIds(String[] ids) {

        if (ids == null || ids.length == 0) return null;

        List<SysDocFile> list = sysDocFileMapper.selectBatchIds(Arrays.asList(ids));

        return list;
    }

    public SysDocFile getSysDocFileByPath(String filePath) {
        SysDocFile sysDocFile = sysDocFileMapper.selectByPath(filePath);
        return sysDocFile;

    }

    private EbsFile getEbsFile(SysDocFile sysDocFile) {

        if (sysDocFile == null) return null;

        String filePath = getLocationDir();
        filePath += sysDocFile.getFilePath();

        String dir = FilenameUtils.getFullPath(filePath);
        mkdirs(dir);

        OutputStream out = null;
        try {
            File file = new File(filePath);
            boolean existsFile = sysDocFile.getId() == null ? false : existsFile(sysDocFile, file);
            if (!existsFile)
                out = new FileOutputStream(file);

            EbsFile ebsFile = getEbsFile(sysDocFile, out, existsFile);

            saveChecksum(sysDocFile, file);
            return ebsFile;
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    private EbsFile getEbsFile(SysDocFile sysDocFile, OutputStream out, boolean existsFile) {

        if (sysDocFile == null) return null;

        String filePath = getLocationDir();
        filePath += sysDocFile.getFilePath();

        try {
            if (!existsFile) {
                downloadFile(sysDocFile.getFilePath(), out, sysDocFile.getServerNode());

                if (StringUtils.isNotEmpty(sysDocFile.getFileChecksum())) {
                    String cacheKey = configServiceImpl.getNodeName() + sysDocFile.getFilePath();
                    CacheManager.set(cacheKey, sysDocFile.getFileChecksum());
                }
            }
            IOUtils.closeQuietly(out);

            EbsFile ebsFile = new EbsFile();
            File file = new File(filePath);
            ebsFile.setFile(file);
            ebsFile.setFileId(sysDocFile.getId());
            ebsFile.setFileName(sysDocFile.getFileName());
            ebsFile.setFilePath(sysDocFile.getFilePath());
            ebsFile.setOwnerUserId(sysDocFile.getOwnerUserId());
            return ebsFile;
        } catch (Exception e) {
            throw new EbsException(e);
        }
    }

    private boolean existsFile(SysDocFile sysDocFile, File file) {

        if (!file.exists()) return false;

        InputStream input = null;
        try {
            input = new FileInputStream(file);
            int fileSize = input.available();
            if (fileSize <= 0) {
                return false;
            }

            String cacheKey = configServiceImpl.getNodeName() + sysDocFile.getFilePath();
            String checksum = CacheManager.get(cacheKey);
            if (StringUtils.isEmpty(checksum)) {
                checksum = createChecksum(input);
            }

            if (StringUtils.equalsIgnoreCase("bid2", sysDocFile.getFileType())) {
                if (StringUtils.equalsIgnoreCase(sysDocFile.getFileChecksum(), checksum) && fileSize >= sysDocFile.getFileSize()) {
                    return true;
                }
            } else {
                if (StringUtils.equalsIgnoreCase(sysDocFile.getFileChecksum(), checksum) && fileSize == sysDocFile.getFileSize()) {
                    return true;
                }
            }

            return false;
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(input);
        }
    }

    private void saveChecksum(SysDocFile sysDocFile, File file) {

        if (StringUtils.isNotEmpty(sysDocFile.getFileChecksum()) || !file.exists() || sysDocFile.getId() == null)
            return;

        InputStream input = null;
        try {
            input = new FileInputStream(file);
            int fileSize = input.available();
            if (fileSize <= 0) {
                return;
            }

            String checksum = createChecksum(input);
            SysDocFile update = new SysDocFile();
            update.setId(sysDocFile.getId());
            update.setFileChecksum(checksum);
            update.setFileSize(fileSize);

            sysDocFileMapper.updateById(update);
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(input);
        }
    }


    private OSSClient getOSSClient() {
        logger.info("ossClient=" + client);
        client = null;
        if (client == null) {
            synchronized (lock) {
                if (client == null) {
                    String accessKeyId = configServiceImpl.getString("access_key_id");
                    String accessKeySecret = configServiceImpl.getString("access_key_secret");
                    endpoint = configServiceImpl.getString("oss_endpoint");
                    bucketName = configServiceImpl.getString("oss_bucketname");
                    client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
                }
            }
        }
        return client;
    }

    private boolean uploadFile(String remoteFilePath, InputStream local) {

        logger.info("OSS上传：存储文件 = " + remoteFilePath);

        while (remoteFilePath.startsWith(separator)) {
            remoteFilePath = StringUtils.substring(remoteFilePath, 1);
        }

        try {
            getOSSClient().putObject(bucketName, remoteFilePath, local);
            return true;
        } catch (Exception e) {
            throw new EbsException(e);
        } finally {
            IOUtils.closeQuietly(local);
        }
    }

    private void downloadFile(String filePath, OutputStream output, String serverNode) throws Exception {

        ExecTime execTime = new ExecTime();
        logger.info("OSS下载：文件路径 =" + filePath);

        while (filePath.startsWith(separator)) {
            filePath = StringUtils.substring(filePath, 1);
        }

        try {
            OSSObject object = getOSSClient().getObject(bucketName, filePath);
            InputStream objectContent = object.getObjectContent();

            IOUtils.copy(objectContent, output);
        } catch (Exception e) {
            throw new Exception(e);
        } finally {
            logger.info("下载文件：" + filePath + " 耗时：" + execTime);
        }
    }

    /**
     * 初始化分块上传事件并生成uploadID，用来作为区分分块上传事件的唯一标识
     */
    public String getUploadId(String fileName) {
        OSSClient client = getOSSClient();
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
        InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
        return result.getUploadId();
    }


    @Override
    public void multipartUpload(InputStream inputStream, String fileName, String uploadId, long partSize, int partNumber) {
        logger.info("文件名称:{},uploadId:{},partSize:{},partNumber:{}", fileName, uploadId, partSize,partNumber);
        //验证当前uploadId partNumber是否已上传
        String partKey = Constants.getUploadPartKey(uploadId+"_"+partNumber);
        String partValue = CacheManager.get(partKey);
        if (StringUtils.isNotBlank(partValue)) {
            return;
        }
        OSSClient client = getOSSClient();
        //文件上传
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(bucketName);
        uploadPartRequest.setKey(fileName);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setInputStream(inputStream);
        uploadPartRequest.setPartSize(partSize);
        uploadPartRequest.setPartNumber(partNumber);
        //上传部分文件
        UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
        PartETag eTag = uploadPartResult.getPartETag();
        //TODO 记录上传的eTag
        String tagKey = Constants.getUploadETagKey(uploadId);
        String tagValue = CacheManager.get(tagKey);
        List<PartETag> tags = new ArrayList<>();
        if (StringUtils.isNotBlank(tagValue)) {
            tags = JSONObject.parseObject(tagValue, new TypeReference<List<PartETag>>() {
            });
        }
        tags.add(eTag);
        CacheManager.set(tagKey, tags);

        //记录上传分片
        CacheManager.set(partKey, partNumber);
    }

    /**
     * 将文件分块进行升序排序并执行文件上传。
     *
     * @param uploadId
     */
    public String completeMultipartUpload(String uploadId, String fileName, String module1, String module2, Long fileSize, String oriFileName, String token) {
        String tagKey = Constants.getUploadETagKey(uploadId);
        String tagValue = CacheManager.get(tagKey);
        if (StringUtils.isBlank(tagValue)) {
            return "";
        }
        List<PartETag> partETags = JSONObject.parseObject(tagValue, new TypeReference<List<PartETag>>() {
        });
        //logger.info("文件名称:{},PartETag:{}",fileName,JSONObject.toJSONString(partETags));
        // 将文件分块按照升序排序
        partETags.sort(new Comparator<PartETag>() {
            @Override
            public int compare(PartETag o1, PartETag o2) {
                return o1.getPartNumber() - o2.getPartNumber();
            }
        });

        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName,
                fileName, uploadId, partETags);
        // 完成分块上传
        getOSSClient().completeMultipartUpload(completeMultipartUploadRequest);

        //删除
        CacheManager.del(tagKey);

        //记录入库 token获取fileId
        String fileId = getUUID();
        SysDocFile sysDocFile = new SysDocFile();
        EntityUtil.setValue(sysDocFile);
        sysDocFile.setId(fileId);
        sysDocFile.setFileName(oriFileName);
        sysDocFile.setFilePath(fileName);
        sysDocFile.setFileSize(fileSize.intValue());
        sysDocFile.setFileType(FilenameUtils.getExtension(oriFileName));
        sysDocFile.setModule1(module1);
        sysDocFile.setModule2(module2);
        sysDocFile.setServerNode(configServiceImpl.getString("ftp_server_node", "a"));
        sysDocFile.setFileChecksum("");
        sysDocFile.setCreateTime(new Date());
        sysDocFileMapper.insert(sysDocFile);
        return fileId;
    }


    public String getFileName(String token, String fileName, String module1, String module2) {
        String fileKey = Constants.getUploadFileKey(token);
        String fileValue = CacheManager.get(fileKey);
        if (StringUtils.isNotBlank(fileValue)) {
            return fileValue;
        }

        String companyCode = getCompanyCode();
        String dateStr = DateFormatUtils.format(new Date(), "yyyyMMdd");
        String fileId = getUUID();
        StringBuilder builder = new StringBuilder(200);
        builder.append(separator);
        builder.append(companyCode);
        builder.append(separator);
        builder.append(dateStr);
        builder.append(separator);
        builder.append(module1);
        builder.append(separator);
        builder.append(module2);
        builder.append(separator);
        builder.append(fileId + "." + FilenameUtils.getExtension(fileName));

        String newFileName = builder.toString();

        while (newFileName.startsWith(separator)) {
            newFileName = StringUtils.substring(newFileName, 1);
        }
        CacheManager.set(fileKey, newFileName);
        return newFileName;
    }


}

